一、痛点引入:为什么需要动态代理?
在传统开发中,为多个方法添加相同的横切逻辑(如日志、权限校验、事务管理),最直观的做法是挨个写调用代码,或者用模板方法模式封装。但试想一个现实场景:你开发了一个线上棋牌平台,需要在每位玩家的出牌、摸牌、胡牌等几十个方法前后记录操作日志。传统的硬编码方式会导致代码冗余严重、维护成本极高——一旦日志格式需要调整,就得逐个方法去修改。

这种痛点催生了动态代理技术的诞生。它允许我们在运行时动态生成代理对象,将横切逻辑与业务逻辑解耦,从根本上解决了代码重复和扩展性问题。
一句话理解:动态代理好比“游戏中的第三方插件”——它不修改原始程序,却能拦截所有操作并自动执行附加功能。

二、核心概念讲解:JDK动态代理
JDK动态代理(Java Dynamic Proxy)是Java原生提供的动态代理技术,核心位于java.lang.reflect包。
工作原理:JDK动态代理基于接口实现。代理类在运行时通过Proxy.newProxyInstance()方法动态生成,该代理类会实现目标对象的所有接口,并在方法调用时通过InvocationHandler将请求转发给目标对象,同时可在转发前后插入增强逻辑。底层依赖Java的反射机制(Reflection)完成方法调用。
生活化类比:JDK动态代理就像一个“正规中介公司”。你(目标对象)持有营业执照(接口),中介(代理对象)也持有同样的营业执照。客户找中介办事,中介先记录日志、再打电话(反射)通知你处理、最后收尾,全程客户以为自己直接找的是你。
代码示例:
// 1. 定义接口(相当于“营业执照”) public interface CardService { void playCard(String card); void drawCard(); } // 2. 目标类实现接口 public class CardServiceImpl implements CardService { @Override public void playCard(String card) { System.out.println("出牌:" + card); } @Override public void drawCard() { System.out.println("摸牌"); } } // 3. 定义InvocationHandler(横切逻辑) public class LogInvocationHandler implements InvocationHandler { private final Object target; public LogInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("[前置日志] 调用方法:" + method.getName()); Object result = method.invoke(target, args); // 反射调用目标方法 System.out.println("[后置日志] 方法执行完毕"); return result; } } // 4. 生成代理对象 public class Main { public static void main(String[] args) { CardService target = new CardServiceImpl(); CardService proxy = (CardService) Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new LogInvocationHandler(target) ); proxy.playCard("一万"); // 输出:[前置日志] 调用方法:playCard → 出牌:一万 → [后置日志] 方法执行完毕 } }
关键要点:JDK动态代理要求目标类必须实现至少一个接口,代理类在运行时生成,代理对象类型由接口列表决定-。
三、关联概念讲解:CGLIB动态代理
CGLIB(Code Generation Library)是一个基于ASM字节码技术的动态代理库,通过动态生成目标类的子类来实现代理。
工作原理:CGLIB不依赖接口。它在运行时使用ASM字节码框架动态生成目标类的子类,并重写父类的非final方法,在重写的方法中植入增强逻辑。由于采用了字节码级别的操作而非反射调用,执行效率更高。
生活化类比:CGLIB就像一个“高科技克隆人工厂”。不管你有没有营业执照,工厂直接提取你的DNA(类结构),克隆出一个长得一模一样的子类,重写你的所有非final方法。客户来找你,实际上面对的是克隆人——它在你干活前后自动加料,而你完全不知情。
代码示例:
// 目标类——无需实现任何接口 public class CardServiceNoInterface { public void playCard(String card) { System.out.println("出牌:" + card); } public final void finalMethod() { System.out.println("这个方法无法被代理"); } } // 定义MethodInterceptor(相当于CGLIB的InvocationHandler) public class LogMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("[前置日志] 调用方法:" + method.getName()); Object result = proxy.invokeSuper(obj, args); // 调用父类方法 System.out.println("[后置日志] 方法执行完毕"); return result; } } // 生成CGLIB代理 public class CglibMain { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(CardServiceNoInterface.class); enhancer.setCallback(new LogMethodInterceptor()); CardServiceNoInterface proxy = (CardServiceNoInterface) enhancer.create(); proxy.playCard("一万"); } }
关键要点:CGLIB无法代理final类和final方法,因为final类不能被继承、final方法不能被重写-。
四、概念关系与区别总结
| 对比维度 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|
| 实现方式 | 基于接口,代理类实现目标接口 | 基于继承,代理类继承目标类 |
| 底层技术 | 反射(Proxy + InvocationHandler) | ASM字节码生成(Enhancer) |
| 接口要求 | 必须实现接口 | 无需接口 |
| 代理限制 | 无法代理无接口的类 | 无法代理final类/final方法 |
| 性能特点 | 代理生成快,方法调用稍慢(反射) | 代理生成稍慢,方法调用更快 |
| 依赖 | Java原生,无需三方库 | 需引入cglib库(Spring已内置) |
| 类名特征 | $Proxy0 | $$EnhancerByCGLIB$$xxx |
一句话总结:JDK动态代理是“接口驱动”的代理,CGLIB是“类继承”的代理——一个要求有营业执照,一个只要有DNA就行-1。
在JDK 8及更高版本中,两者的性能差距已显著缩小,现代JVM对字节码优化良好-1。
五、Spring AOP中的代理选择机制
Spring AOP(Aspect-Oriented Programming,面向切面编程)是动态代理技术在Spring框架中的典型应用。其核心实现依赖于ProxyFactory,它会根据目标类的特征自动选择合适的代理方式-35:
有接口时:默认使用JDK动态代理(
JdkDynamicAopProxy)无接口时:自动切换为CGLIB代理(
ObjenesisCglibAopProxy)
也可以通过配置强制指定代理方式:
<!-- XML配置 --> <aop:config proxy-target-class="true"/>
// Java配置 @EnableAspectJAutoProxy(proxyTargetClass = true)
Spring 5.2+默认启用Objenesis构造代理对象,避免调用目标类的构造器——这点常被忽略,可能导致自定义构造逻辑失效-14。
六、底层原理支撑
动态代理的底层实现依赖两个关键技术:
反射机制:JDK动态代理在运行时通过
Method.invoke()调用目标方法,反射机制赋予了Java在运行时“看清”类结构并动态调用方法的能力。ASM字节码技术:CGLIB使用ASM框架在运行时动态生成Java字节码并加载为类,本质是在JVM层面“凭空造出”一个类。
批量代理的实现逻辑:Spring AOP利用切点表达式(如execution( com.example.service..(..)))匹配多个目标类,容器初始化时扫描匹配的Bean并统一为其生成动态代理,无需为每个目标类单独编写代理代码-31。
七、高频面试题
Q1:JDK动态代理和CGLIB有什么区别?各自的优缺点是什么?
参考答案要点:
实现原理:JDK基于接口+反射,代理类实现目标接口;CGLIB基于继承+字节码,代理类继承目标类-1。
依赖条件:JDK要求目标类必须实现接口;CGLIB无接口要求,但不能代理final类/final方法-。
性能:JDK代理生成快但调用略慢;CGLIB代理生成慢但调用更快(JDK 8+差距已缩小)-1。
应用场景:面向接口编程优先用JDK;需要代理无接口类时用CGLIB。Spring AOP默认结合两者使用。
Q2:Spring AOP的实现原理是什么?
参考答案要点:
核心机制:基于动态代理(JDK Proxy + CGLIB),在运行时将切面逻辑织入目标方法-11。
代理选择:ProxyFactory根据目标类是否实现接口自动选择代理方式——有接口用JDK,无接口用CGLIB-14。
织入时机:发生在IoC容器初始化阶段,属于运行时织入-35。
Q3:为什么JDK动态代理只能代理接口实现类?
参考答案要点:JDK动态代理生成的代理类默认继承了java.lang.reflect.Proxy,而Java不支持多继承,因此代理类无法再继承目标类,只能通过实现接口的方式来“代表”目标对象-。
Q4:如何强制Spring AOP使用CGLIB代理?
参考答案要点:XML配置使用<aop:config proxy-target-class="true"/>;注解配置使用@EnableAspectJAutoProxy(proxyTargetClass = true)-14。
Q5:动态代理的“动态”体现在哪里?
参考答案要点:代理类不是在编译期手动编写,而是在运行时根据接口或目标类动态生成字节码并加载。无论多少个目标对象,只需一套横切逻辑即可动态生成代理,无需重复编码-31。
八、总结回顾
JDK动态代理:Java原生,基于接口+反射,要求目标类有接口,代理生成快。
CGLIB动态代理:基于字节码+继承,无需接口,但无法代理final类/方法,方法调用性能更优。
Spring AOP:结合两种代理方式,根据目标类特征自动选择,实现了横切逻辑与业务逻辑的解耦。
底层依赖:反射 + ASM字节码技术是动态代理的两大基石。
⚠️ 常见易错点:
混淆JDK和CGLIB的适用场景——记住“接口用JDK,无接口用CGLIB”
忽视CGLIB的final限制——用CGLIB代理前确认目标类和方法不是final
忘记Spring AOP默认只对public方法生效
同类内部方法自调用会导致代理失效(需通过代理对象调用而非this)
下一篇将深入探讨Spring AOP失效的7种场景及解决方案,敬请期待!
📌 本文内容基于2026年4月Spring Framework 5.x / 6.x版本编写,相关配置细节请以官方文档为准。